/**
 *
 * RIP - Routing Information Protocol > baseado na implementação de Kurose-Ross
 *
 * Laboratório de Redes de Computadores Bacharelado em Ciência da Computação -
 * UFSCar, Sorocaba
 *
 * Alunos: Nahim Alves de Souza RA 380059 Rafael Cláudio Domene RA 345040
 *
 * Professora: Yeda Regina Venturini
 *
 */
package rip;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.Random;
import nodes.node0;
import nodes.node1;
import nodes.node2;
import nodes.node3;

public class RIP {

    // indica quando há mudanças nos custos
    private int LINKCHANGES = 1;
    // usado para debug
    public int TRACE;
    // variaveis usadas para indicar o tipo dos eventos
    private static final int FROM_LAYER2 = 2;
    private static final int LINK_CHANGE = 10;
    private double clocktime = 0.000;
    private PriorityQueue<Event> evList = new PriorityQueue<>();
    // Cria os nós que serão usados
    private node0 node0 = new node0();
    private node1 node1 = new node1();
    private node2 node2 = new node2();
    private node3 node3 = new node3();

    public void start() throws IOException {
        Event event = new Event();

        // Inicializacao das tabelas de roteamento
        init();

        while (true) {

            event = evList.poll(); // Pega o próximo evento a ser simulado e remove da lista
            if (event == null) {
                break; // sai do while
            }
            if (this.TRACE > 1) {
                System.out.println("MAIN: recieve event, t=" + event.getEvTime() + ", at " + event.getEvEntity());
                if (event.getEvType() == FROM_LAYER2) {
                    System.out.println(" src: " + event.getRp().getSourceID());
                    System.out.println(" dest: " + event.getRp().getDestID());
                    // obs: minCost é um vetor, por isso tem um indice entre []
                    System.out.println(" contents: " + event.getRp().getMinCost(0) + " " + event.getRp().getMinCost(1)
                            + " " + event.getRp().getMinCost(2) + " " + event.getRp().getMinCost(3));
                }
            }

            clocktime = event.getEvTime();       // pega o tempo do proximo evento
            if (event.getEvType() == FROM_LAYER2) {
                if (event.getEvEntity() == 0) {
                    node0.rtUpdate0(event.getRp(), this);
                } else if (event.getEvEntity() == 1) {
                    node1.rtUpdate1(event.getRp(), this);
                } else if (event.getEvEntity() == 2) {
                    node2.rtUpdate2(event.getRp(), this);
                } else if (event.getEvEntity() == 3) {
                    node3.rtUpdate3(event.getRp(), this);
                } else {
                    System.out.println("Panic: unknown event entity\n");
                    return;
                }
            } else if (event.getEvType() == LINK_CHANGE) {
                if (clocktime < 10001.0) {
                    node0.linkHandler0(1, 20);
                    node1.linkHandler1(0, 20);
                } else {
                    node0.linkHandler0(1, 1);
                    node1.linkHandler1(0, 1);
                }
            } else {
                System.out.println("Panic: unknown event type\n");
                return;
            }
        } // fim while        
        
        //terminate:
        System.out.println("\nSimulator terminated at " + clocktime + ", no packets in medium\n");

    }

    private void init() throws IOException {

        Event event = new Event();

        // Cria a tabela de custos inicial
        int[][] initialTable = new int[4][4];
        initialTable[0][0] = 0;
        initialTable[0][1] = 1;
        initialTable[0][2] = 3;
        initialTable[0][3] = 7;
        initialTable[1][0] = 1;
        initialTable[1][1] = 0;
        initialTable[1][2] = 1;
        initialTable[1][3] = 999;
        initialTable[2][0] = 3;
        initialTable[2][1] = 1;
        initialTable[2][2] = 0;
        initialTable[2][3] = 2;
        initialTable[3][0] = 7;
        initialTable[3][1] = 999;
        initialTable[3][2] = 2;
        initialTable[3][3] = 0;

        // Para fazer a leitura pelo teclado
        InputStream is = System.in;
        InputStreamReader isr = new InputStreamReader(is);
        BufferedReader br = new BufferedReader(isr);

        // Lê o valor de TRACE:
        System.out.println("Informe o TRACE: ");
        char[] c = br.readLine().toCharArray();
        // Para converter o caractere para inteiro
        this.TRACE = c[0] - '0';

        // Inicializa o relogio
        clocktime = 0.00;

        // Inicializa os nós
        node0.rtInit0(this);
        node1.rtInit1(this);
        node2.rtInit2(this);
        node3.rtInit3(this);

        // Inicializa as futuras mudanças de custos
        if (LINKCHANGES == 1) {
            // Cria uma mudança de custo
            event.setEvTime(10000);      // tempo 10000
            event.setEvType(LINK_CHANGE);  // indica que é uma mudança de custo 
            event.setEvEntity(-1);         // indica que a mudança não foi produzida por nenhum nó
            event.setRp(null);             // não possui pacote de rotina, pq os nós devem identificar a mudança
            insertEvent(event);            // insere o evento na lista

            // Cria outra mudança de custo semelhante ao de cima
            event.setEvTime(20000);
            event.setEvType(LINK_CHANGE);
            event.setEvEntity(-1);
            event.setRp(null);
            insertEvent(event);
        }

    }

    private boolean insertEvent(Event e) {
        // Insere um evento na lista, ORDENANDO pela hora em que o evento ocorreu (EvTime)        

        if (this.TRACE > 3) {
            System.out.println("INSERTEVENT: time is: " + clocktime);
            System.out.println("INSERTEVENT: future time will be: " + e.getEvTime());
        }

        return evList.add(e); 
    }

    private void printEvList() {
        // Imprime os dados de todos os eventos da lista

        Iterator<Event> it = evList.iterator();
        Event e;

        while (it.hasNext()) {
            e = it.next();
            System.out.println("Event time: " + e.getEvTime() + ", type: " + e.getEvType() + ", entity: " + e.getEvEntity());
        }
    }

    public void toLayer2(rtpkt packet) {

        /* apenas confere se os IDs de origem e destino estão corretos */
        if (packet.sourceID < 0 || packet.sourceID > 3) {
            System.out.println("WARNING: illegal source id in your packet, ignoring packet!\n");
            return;
        }
        if (packet.destID < 0 || packet.destID > 3) {
            System.out.println("WARNING: illegal dest id in your packet, ignoring packet!\n");
            return;
        }
        if (packet.sourceID == packet.destID) {
            System.out.println("WARNING: source and destination id's the same, ignoring packet!\n");
            return;
        }
        // verifica os dois nós que não são vizinhos diretos, isto é, 1 e 3
        if ((packet.sourceID == 1 && packet.destID == 3) || (packet.sourceID == 3 && packet.destID == 1)) {
            System.out.println("WARNING: source and destination not connected, ignoring packet!\n");
            return;
        }

        // Para exibir dados na tela
        if (this.TRACE > 2) {
            System.out.println("\nTOLAYER2: ORIGEM: " + packet.sourceID + ", DESTINO: " + packet.destID + "\n CUSTOS MINIMOS:");
            for (int i = 0; i < 4; i++) {
                System.out.println(" De " + packet.sourceID + " Para " + i + ": " + packet.minCost[i]);
            }
            //System.out.print(" ");
        }

        // Cria um evento futuro que vai indicar a chegada do pacote no outro nó
        Event event = new Event();
        event.setEvType(FROM_LAYER2);
        event.setEvTime(packet.destID);
        event.setRp(packet);

        // Calcula o tempo de chegada do pacote e insere na lista de evento
        double lastime = clocktime;
        Iterator<Event> it = evList.iterator();
        Event ev; // auxiliar
        while (it.hasNext()) {
            ev = it.next();
            if (ev.getEvType() == FROM_LAYER2 && ev.getEvEntity() == event.getEvEntity()) {
                lastime = ev.getEvTime();
            }
        }

        // define o tempo da chegada do pacote
        Random rand = new Random();
        event.setEvTime( (float)lastime + 2 * (float)rand.nextDouble() );

        // Insere o evento na lista ordenada
        insertEvent(event);
    }
}
